package com.kkwrite.regimen.consumer.controller.order;

import com.kkwrite.regimen.common.entity.dto.OutDTO;
import com.kkwrite.regimen.common.entity.dto.order.OrderChangePayStatusInDTO;
import com.kkwrite.regimen.common.entity.dto.order.OrderReviewSubmitInDTO;
import com.kkwrite.regimen.common.entity.dto.order.SubmitOrderInDTO;
import com.kkwrite.regimen.common.entity.dto.order.SubmitOrderOutDTO;
import com.kkwrite.regimen.consumer.common.AES256Util;
import com.kkwrite.regimen.consumer.common.HttpClientUtil;
import com.kkwrite.regimen.consumer.common.WXApi;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.dom4j.DocumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import springfox.documentation.annotations.ApiIgnore;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import static com.kkwrite.regimen.common.constant.Constant.BOOLEAN_STRING_FALSE;
import static com.kkwrite.regimen.common.constant.Constant.BOOLEAN_STRING_TRUE;
import static com.kkwrite.regimen.consumer.common.WXApi.ERR_CODE;
import static com.kkwrite.regimen.consumer.common.WXApi.ERR_CODE_DES;
import static com.kkwrite.regimen.consumer.common.WXApi.RESULT_CODE;
import static com.kkwrite.regimen.consumer.common.WXApi.RETURN_CODE;
import static com.kkwrite.regimen.consumer.common.WXApi.RETURN_MSG;
import static com.kkwrite.regimen.consumer.common.WXApi.SUCCESS;
import static com.kkwrite.regimen.consumer.controller.constant.Constant.SERVICE_PREFIX;

/** 
 * 订单模块
 *
 * @author Soosky Wang
 * @date 2018年7月21日 下午8:26:21 
 * @version 1.0.0
 */
@Api(value = "订单模块")
@RestController
@RequestMapping("/order")
public class OrderController {

	private static final Logger logger = LoggerFactory.getLogger(OrderController.class);
	
	@Resource(name = "restTemplate")
	private RestTemplate restTemplate;
	@Resource
	private StringRedisTemplate stringRedisTemplate;
	@Resource(name = "myHttpClient")
	private HttpClientUtil httpClient;

	@PostMapping(value = "/changepaystatus")
	@HystrixCommand(fallbackMethod = "changeOrderPayStatusError")
	@ApiOperation(value = "更新订单支付状态接口")
	@ApiImplicitParam(name = "inDTO", value = "需要订单编号和支付状态", required = true,
			dataType = "OrderChangePayStatusInDTO", paramType = "body")
	@ApiIgnore
	public OutDTO changeOrderPayStatus(OrderChangePayStatusInDTO inDTO) {
		if (inDTO.isEmpty()) {
			throw new RuntimeException("入参不合法");
		}
		return restTemplate.postForObject(SERVICE_PREFIX + "/order/changePayStatus", inDTO, OutDTO.class);
	}

	public OutDTO changeOrderPayStatusError(OrderChangePayStatusInDTO inDTO, Throwable t) {
		logger.error("更新订单支付状态异常！异常信息：" + t.getMessage(), t);
		return OutDTO.of(BOOLEAN_STRING_FALSE, "更新订单支付状态异常！异常信息：" + t.getMessage());
	}

	@GetMapping(value = "/count")
	@HystrixCommand(fallbackMethod = "orderCountError")
	@ApiOperation(value = "查询订单汇总信息", notes = "后台依据token获取userId做查询")
	public OutDTO orderCount(@ApiIgnore @RequestHeader(name = "token") String token,
							 @RequestParam(required = false) Short orderType) {
		String userId = (String) stringRedisTemplate.opsForHash().get(token, "userId");
		if (userId == null) {
			return OutDTO.of(BOOLEAN_STRING_FALSE, "002", "token invalid", null);
		}
		String url = SERVICE_PREFIX + "/order/count?userId=" + userId;
		if (orderType != null) {
			url += "&orderType=" + orderType;
		}
		return restTemplate.getForObject(url, OutDTO.class);
	}

	public OutDTO orderCountError(String token, Short orderType, Throwable throwable) {
		logger.error("查询订单汇总信息异常，异常信息：" + throwable.getMessage(), throwable);
		return OutDTO.of(BOOLEAN_STRING_FALSE, "服务异常！" + throwable.getMessage());
	}

	@GetMapping(value = "/wxstatus/{orderCode}")
	@HystrixCommand(fallbackMethod = "checkWechatPayStatusError")
	@ApiOperation(value = "查看微信订单状态")
	@ApiImplicitParam(name = "orderCode", value = "订单编号", required = true, paramType = "path", dataType = "string")
	public OutDTO checkWechatPayStatus(@PathVariable String orderCode) {
		if (StringUtils.isBlank(orderCode)) {
			return OutDTO.of(BOOLEAN_STRING_FALSE, "入参不合法");
		}
		// 调用微信API查看订单状态
		Map<String, String> map = new HashMap<>(16);
		map.put("appid", WXApi.APP_ID);
		map.put("mch_id", WXApi.MCH_ID);
		map.put("out_trade_no", orderCode);
		map.put("nonce_str", WXApi.generateNonceStr());
		map.put("sign", WXApi.generateSign(map));
		RestTemplate rest = new RestTemplate();
		rest.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
		String resultXmlString = rest.postForObject(WXApi.PAY_ORDER_QUERY, WXApi.map2XmlString(map), String.class);
		if (logger.isDebugEnabled()) {
			logger.debug("查询订单 {} 支付状态，返回xml字符串：{}", orderCode, resultXmlString);
		}
		map.clear();
		map = WXApi.convertString2Map(resultXmlString);
		if (!SUCCESS.equals(map.get(RETURN_CODE))) {
			logger.error("订单 {} 支付状态查询失败！返回信息：{}", orderCode, map.get(RETURN_MSG));
			return OutDTO.of(BOOLEAN_STRING_FALSE, "查询失败！错误信息：" + map.get(RETURN_MSG));
		}
		String returnSign = map.get("sign");
		map.remove("sign");
		if (!returnSign.equals(WXApi.generateSign(map))) {
			logger.warn("微信查询订单接口调用签名校验不通过！订单编号：{}，接口返回xml字符串：{}", orderCode, resultXmlString);
			return OutDTO.of(BOOLEAN_STRING_FALSE, "微信接口返回签名校验不通过");
		}
		if (!SUCCESS.equals(map.get(RESULT_CODE))) {
			logger.warn("微信查询订单出错！订单号：{}，错误码：{}，错误信息：{}", orderCode, map.get(ERR_CODE), map.get(ERR_CODE_DES));
			return OutDTO.of(BOOLEAN_STRING_FALSE, "微信订单查询出错！错误信息：" + map.get(ERR_CODE_DES));
		}
		try {
			restTemplate.postForObject(SERVICE_PREFIX + "/order/updateTradeState", map, String.class);
		} catch (Exception e) {
			logger.error("查询微信订单状态接口，更新后台订单支付状态失败！异常信息：{}", e.getMessage());
		}
		return OutDTO.of(BOOLEAN_STRING_TRUE, null, null, map.get("trade_state"));
	}

	public OutDTO checkWechatPayStatusError(String orderCode) {
		return OutDTO.of(BOOLEAN_STRING_FALSE, "服务异常！");
	}

	@ApiIgnore
	@RequestMapping(value = "/refund/wxpaynotify")
	public void orderRefundNotify(HttpServletRequest request, HttpServletResponse response) {
		try {
			request.setCharacterEncoding("UTF-8");
		} catch (UnsupportedEncodingException e) {
			logger.error(e.getMessage(), e);
		}
		Map<String, String> resultMap = new HashMap<>(16);
		try {
			WXApi.convertXml2Map(resultMap, request.getInputStream());
		} catch (DocumentException | IOException e) {
			logger.error(e.getMessage(), e);
			notifyError(response);
			return;
		}
		if (!SUCCESS.equals(resultMap.get(RETURN_CODE))) {
			logger.error("退款处理失败，微信返回错误信息：{}", resultMap.get(RETURN_MSG));
			notifySuccess(response);
			return;
		}
		String reqInfo = resultMap.get("req_info");
		String key = DigestUtils.md5Hex(WXApi.WX_PAY_KEY);
		String result;
		try {
			result = AES256Util.decrypt(reqInfo, key);
			logger.info("微信退款通知加密串：{} \n 解密结果：\n{}", reqInfo, result);
		} catch (GeneralSecurityException e) {
			logger.error("微信退款通知加密串解密异常，异常信息：" + e.getMessage(), e);
			notifyError(response);
			return;
		}
		// 更新后台订单表数据
		try {
			OutDTO out = restTemplate.postForObject(SERVICE_PREFIX + "/order/refundNotifyUpdate", WXApi.convertString2Map(result), OutDTO.class);
			if (!out.getSuccess().equals(BOOLEAN_STRING_TRUE)) {
				throw new RuntimeException(out.getMsg());
			}
		} catch (Exception e) {
			logger.error("微信退款通知更新订单表异常，异常信息：" + e.getMessage(), e);
			notifyError(response);
			return;
		}
		notifySuccess(response);
	}

	@GetMapping(value = "/refund/{orderCode}")
	@HystrixCommand(fallbackMethod = "orderRefundError", commandProperties = {
			@HystrixProperty(name = "execution.timeout.enabled", value = "false")
	})
	@ApiOperation(value = "订单退款接口", notes = "对支付后取消的订单发起退款")
	@ApiImplicitParam(name = "orderCode", value = "订单编号", required = true, dataType = "String", paramType = "path")
	public OutDTO orderRefund(@PathVariable String orderCode) {
		if (StringUtils.isBlank(orderCode)) {
			return OutDTO.of(BOOLEAN_STRING_FALSE, "入参不合法");
		}
		OutDTO out = restTemplate.getForObject(SERVICE_PREFIX + "/order/refund/" + orderCode, OutDTO.class);
		if (out.getSuccess().equals(BOOLEAN_STRING_FALSE)) {
			return out;
		}
		Map data = (Map) out.getData();
		String transactionId = (String) data.get("transactionId");
		String amount = (String) data.get("amount");
		String refundCode = (String) data.get("refundCode");
		Map<String, String> map = new HashMap<>(16);
		map.put("appid", WXApi.APP_ID);
		map.put("mch_id", WXApi.MCH_ID);
		map.put("nonce_str", WXApi.generateNonceStr());
		map.put("transaction_id", transactionId);
		map.put("out_trade_no", orderCode);
		map.put("out_refund_no", refundCode);
		map.put("total_fee", amount);
		map.put("refund_fee", amount);
		map.put("notify_url", WXApi.REFUND_NOTIFY_URL);
		String sign = WXApi.generateSign(map);
		map.put("sign", sign);
		String xml = WXApi.map2XmlString(map);

		if (logger.isInfoEnabled()) {
			logger.info("请求xml: \n" + xml);
		}

		Map<String, String> result;
		try {
			String xmlResult = httpClient.post(WXApi.PAY_REFUND, xml);

			logger.info("微信退款接口返回xml：\n {}", xmlResult);

			result = WXApi.convertString2Map(xmlResult);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			return OutDTO.of(BOOLEAN_STRING_FALSE, "调用微信申请退款接口异常或解析调用结果异常！");
		}
		if (!"SUCCESS".equals(result.get("return_code"))) {
			logger.error("调用微信申请退款接口返回失败，返回信息：{}", result.get("return_msg"));
			return OutDTO.of(BOOLEAN_STRING_FALSE, result.get("return_msg"));
		}
		String returnSign = result.get("sign");
		result.remove("sign");
		if (!returnSign.equals(WXApi.generateSign(result))) {
			return OutDTO.of(BOOLEAN_STRING_FALSE, "微信申请退款接口返回签名校验不通过");
		}
		if (!"SUCCESS".equals(result.get("result_code"))) {
			if (logger.isWarnEnabled()) {
				logger.warn("向微信申请退款失败，错误码：{}，错误提示：{}", result.get("err_code"), result.get("err_code_des"));
			}
			return OutDTO.of(BOOLEAN_STRING_FALSE, "向微信申请退款失败，错误提示：" + result.get("err_code_des"));
		}
		return OutDTO.of(BOOLEAN_STRING_TRUE, "退款已申请");
	}

	public OutDTO orderRefundError(String orderCode, Throwable throwable) {
		logger.error("调用退款接口异常：" + throwable.getMessage(), throwable);
		return OutDTO.of(BOOLEAN_STRING_FALSE, "服务异常！");
	}

	@GetMapping(value = "/querypageable")
	@HystrixCommand(fallbackMethod = "queryOrderPageableError")
	@ApiOperation(value = "分页查询订单列表接口")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "page", value = "页码", required = true, dataType = "int",
					paramType = "query", allowableValues = "[0, infinity)"),
			@ApiImplicitParam(name = "pageSize", value = "每页条目数", required = true, dataType = "int",
					paramType = "query", allowableValues = "[1, infinity)"),
			@ApiImplicitParam(name = "status", value = "订单状态", dataType = "int",
					paramType = "query", allowableValues = "1,2,3,4"),
            @ApiImplicitParam(name = "orderType", value = "订单类型", dataType = "int",
                    paramType = "query", allowableValues = "1,2,3")
	})
	public OutDTO queryOrderPageable(@RequestParam Integer page, @RequestParam Integer pageSize,
									 @RequestParam(required = false) Integer status,
									 @RequestParam(required = false) Short orderType,
                                     HttpServletRequest request) {
		String token = request.getHeader("token");
		String userId = (String) stringRedisTemplate.opsForHash().get(token, "userId");
		if (userId == null) {
			return OutDTO.of(BOOLEAN_STRING_FALSE, "002", "token invalid", null);
		}
		if (page == null || page < 0 || pageSize == null || pageSize < 1) {
			return OutDTO.of(BOOLEAN_STRING_FALSE, "入参不合法");
		}
		String url = SERVICE_PREFIX + "/order/queryPageable?page=" + page + "&pageSize=" + pageSize + "&userId=" + userId;
		if (status != null) {
			if (!Arrays.asList(1, 2, 3, 4).contains(status)) {
				return OutDTO.of(BOOLEAN_STRING_FALSE, "入参不合法");
			}
			url += "&status=" + status;
		}
		// orderType 订单类型；1-项目订单，2-套餐订单，3-活动订单，...
		if (orderType != null) {
			url += "&orderType=" + orderType;
		}
		return restTemplate.getForObject(url, OutDTO.class);
	}

	public OutDTO queryOrderPageableError(Integer page, Integer pageSize, Integer status,
                                          Short orderType, HttpServletRequest request, Throwable t) {
		logger.error("分页查询订单列表异常！异常信息：" + t.getMessage(), t);
		return OutDTO.of(BOOLEAN_STRING_FALSE, "分页查询订单列表异常！异常信息：" + t.getMessage());
	}

	@GetMapping(value = "/service/{orderCode}")
	@HystrixCommand(fallbackMethod = "changeServiceStatusError")
	@ApiOperation(value = "订单服务状态变更接口", notes = "type为0服务结束，type为1服务开始")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "orderCode", value = "订单编号", required = true, paramType = "path", dataType = "String"),
			@ApiImplicitParam(name = "type", value = "变更类别，type为0服务结束，type为1服务开始", required = true, paramType = "query", dataType = "int")
	})
	public OutDTO changeServiceStatus(@PathVariable String orderCode, @RequestParam Integer type) {
		if (StringUtils.isBlank(orderCode)) {
			return OutDTO.of(BOOLEAN_STRING_FALSE, "入参不合法");
		}
		if (type != 0 && type != 1) {
			return OutDTO.of(BOOLEAN_STRING_FALSE, "入参不合法");
		}
		return restTemplate.getForObject(SERVICE_PREFIX + "/order/service/" + orderCode + "?type=" + type, OutDTO.class);
	}

	public OutDTO changeServiceStatusError(String orderCode, Integer type) {
		return new OutDTO(BOOLEAN_STRING_FALSE, "服务异常！");
	}

	@GetMapping(value = "/cancel/{orderCode}")
	@HystrixCommand(fallbackMethod = "cancelOrderError")
	@ApiOperation(value = "订单取消接口")
	@ApiImplicitParam(name = "orderCode", value = "订单编号", required = true, paramType = "path", dataType = "String")
	public OutDTO cancelOrder(@PathVariable String orderCode) {
		if (StringUtils.isBlank(orderCode)) {
			return OutDTO.of("false", "入参不合法");
		}
		return restTemplate.getForObject(SERVICE_PREFIX + "/order/cancel/" + orderCode, OutDTO.class);
	}

	public OutDTO cancelOrderError(String orderCode) {
		return new OutDTO("false", "服务异常！");
	}

	@GetMapping(value = "/continuepay/{orderCode}")
	@HystrixCommand(fallbackMethod = "continuePayError")
	@ApiOperation(value = "订单继续支付接口")
	@ApiImplicitParam(name = "orderCode", value = "订单编号", required = true, paramType = "path", dataType = "String")
	public OutDTO continuePay(@PathVariable String orderCode, HttpServletRequest request) {
		String openId = (String) stringRedisTemplate.opsForHash().get(request.getHeader("token"), "openId");
		if (StringUtils.isBlank(openId)) {
			return OutDTO.of(BOOLEAN_STRING_FALSE, "002", "token invalid", null);
		}
		if (StringUtils.isBlank(orderCode)) {
			return OutDTO.of(BOOLEAN_STRING_FALSE, "入参不合法");
		}
		OutDTO out = restTemplate.getForObject(SERVICE_PREFIX + "/order/continuePay/" + orderCode, OutDTO.class);
		if (BOOLEAN_STRING_FALSE.equals(out.getSuccess())) {
			return out;
		}
		Map map = (Map) out.getData();
		String newCode = (String) map.get("orderCode");
		String totalFee = (String) map.get("totalFee");
		String createIp = request.getHeader("X-Real-IP");
		return payUnifiedOrder(newCode, totalFee, openId, createIp);
	}

	public OutDTO continuePayError(String orderCode, HttpServletRequest request) {
		return OutDTO.of(BOOLEAN_STRING_FALSE, "服务异常！", null, null);
	}

	@PostMapping(value = "/submit")
	@HystrixCommand(fallbackMethod = "submitOrderError")
	@ApiOperation(value = "订购提交订单接口")
	@ApiImplicitParam(name = "inDTO", value = "提交数据", required = true, paramType = "body", dataType = "SubmitOrderInDTO")
	public OutDTO submitOrder(@RequestBody SubmitOrderInDTO inDTO, HttpServletRequest request) {
		String token = request.getHeader("token");
		Object userObj = stringRedisTemplate.opsForHash().get(token, "userId");
		String openId = (String) stringRedisTemplate.opsForHash().get(token, "openId");
		if (userObj == null || openId == null) {
			return OutDTO.of(BOOLEAN_STRING_FALSE, "002", "token invalid", null);
		}
		if (inDTO.isNotOk()) {
			return OutDTO.of(BOOLEAN_STRING_FALSE, "入参不合法", null, null);
		}
		// 调用后台服务保存订单
		OutDTO out = restTemplate.postForObject(SERVICE_PREFIX + "/order/submit/" + userObj, inDTO, OutDTO.class);
		if (StringUtils.isBlank(out.getSuccess()) || BOOLEAN_STRING_FALSE.equals(out.getSuccess())) {
			return new OutDTO<>(BOOLEAN_STRING_FALSE, "订单服务异常！");
		}
		String orderCode = (String) out.getData();
		String fee = String.valueOf((int) (inDTO.getPrice() * 100) * inDTO.getNum());
		String createIp = request.getHeader("X-Real-IP");
		return payUnifiedOrder(orderCode, fee, openId, createIp);
	}

	public OutDTO<SubmitOrderOutDTO> submitOrderError(SubmitOrderInDTO inDTO, HttpServletRequest request) {
		return new OutDTO<>("false", "服务异常！");
	}

	@GetMapping("/getdetailorder/{orderCode}")
	@HystrixCommand(fallbackMethod = "orderDetailError")
	@ApiOperation(value = "根据订单编号查询订单详情")
	@ApiImplicitParam(name = "orderCode", value = "订单编号", required = true, paramType = "path", dataType = "String")
	public OutDTO orderDetail(@PathVariable String orderCode) {
		if (StringUtils.isBlank(orderCode)) {
			return new OutDTO("false", "入参不合法");
		}
		return restTemplate.getForObject(SERVICE_PREFIX + "/order/detail/" + orderCode, OutDTO.class);
	}

	public OutDTO orderDetailError(String orderCode) {
		return new OutDTO(BOOLEAN_STRING_FALSE, "服务异常！");
	}

	@PostMapping(value = "/submitreview")
	@HystrixCommand(fallbackMethod = "submitReviewError")
	@ApiOperation(value = "订单评价提交")
	@ApiImplicitParam(name = "reviewData", value = "评价数据", required = true, dataType = "OrderReviewSubmitInDTO", paramType = "body")
	public OutDTO submitReview(@RequestBody OrderReviewSubmitInDTO reviewData) {
		if (reviewData.isEmpty()) {
			return new OutDTO("false", "入参不合法");
		}
		return restTemplate.postForObject(SERVICE_PREFIX + "/order/submitReview", reviewData, OutDTO.class);
	}

	public OutDTO submitReviewError(OrderReviewSubmitInDTO reviewData) {
		return new OutDTO("false", "服务异常！");
	}

	@GetMapping(value = "/review/{orderCode}")
	@HystrixCommand(fallbackMethod = "queryOrderReviewError")
	@ApiOperation(value = "根据订单编号查询订单评价")
	@ApiImplicitParam(name = "orderCode", value = "订单编号", required = true, dataType = "String", paramType = "path")
	public OutDTO queryOrderReview(@PathVariable String orderCode) {
		if (StringUtils.isBlank(orderCode)) {
			return new OutDTO("false", "入参不合法");
		}
		return restTemplate.getForObject(SERVICE_PREFIX + "/order/review/" + orderCode, OutDTO.class);
	}

	public OutDTO queryOrderReviewError(String orderCode) {
		return new OutDTO("false", "服务异常！");
	}

	@RequestMapping(value = "/wxpaynotify")
	@ApiIgnore
	public void wxpayNotify(HttpServletRequest request, HttpServletResponse response) {

		if (logger.isInfoEnabled()) {
			logger.info("接收到微信的支付通知回调：");
		}

		try {
			request.setCharacterEncoding(StandardCharsets.UTF_8.toString());
		} catch (UnsupportedEncodingException e) {
			logger.error(e.getMessage(), e);
		}
		Map<String, String> map = new HashMap<>(16);
		try {
			WXApi.convertXml2Map(map, request.getInputStream());
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			notifyError(response);
			return;
		}

		if (logger.isInfoEnabled()) {
			map.forEach((k, v) -> logger.info(k + "=" + v));
		}

		if (!"SUCCESS".equals(map.get("return_code"))) {
			logger.error(map.get("return_msg"));
			notifySuccess(response);
			return;
		}

		// 微信返回的签名
		String sign = map.get("sign");
		map.remove("sign");
		// 校验签名
		String mySign = WXApi.generateSign(map);
		if (!mySign.equals(sign)) {
			logger.error("订单({})签名校验失败！微信返回签名：{}，生成签名：{}", map.get("out_trade_no"), sign, mySign);
			notifyError(response);
			return;
		}
		if (!"SUCCESS".equals(map.get("result_code"))) {
			logger.error("订单 {} 支付失败！错误代码：{}， 错误信息：{}", map.get("out_trade_no"), map.get("err_code"), map.get("err_code_des"));
			notifySuccess(response);
			return;
		}
		// 更新订单状态
		try {
			OutDTO out = restTemplate.postForObject(SERVICE_PREFIX + "/order/update/payStatus", map, OutDTO.class);
			if (out == null || out.getSuccess().equals(BOOLEAN_STRING_FALSE)) {
				throw new Exception();
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			notifyError(response);
			return;
		}
		notifySuccess(response);
	}

	private void notifyError(HttpServletResponse response) {
		String xml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[ERROR]]></return_msg></xml>";
		try (PrintWriter out = response.getWriter()) {
			out.write(xml);
			out.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void notifySuccess(HttpServletResponse response) {
		String xml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
		try (PrintWriter out = response.getWriter()) {
			out.write(xml);
			out.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public OutDTO payUnifiedOrder(String orderCode, String fee, String openId, String createIp) {
		// 微信 统一下单接口
		String appId = WXApi.APP_ID;
		String mchId = WXApi.MCH_ID;
		String nonceStr = WXApi.generateNonceStr();
		String body = "调调健康订单支付";
		String notifyUrl = WXApi.WX_PAY_NOTIFY_URL;
		String tradeType = WXApi.TRADE_TYPE;
		Map<String, String> signMap = new HashMap<>(16);
		signMap.put("appid", appId);
		signMap.put("mch_id", mchId);
		signMap.put("nonce_str", nonceStr);
		signMap.put("body", body);
		signMap.put("out_trade_no", orderCode);
		signMap.put("total_fee", fee);
		signMap.put("spbill_create_ip", createIp);
		signMap.put("notify_url", notifyUrl);
		signMap.put("trade_type", tradeType);
		signMap.put("openid", openId);
		String sign = WXApi.generateSign(signMap);
		signMap.put("sign", sign);
		String xml = WXApi.map2XmlString(signMap);
		RestTemplate rest = new RestTemplate();
		rest.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
		String resultXml = rest.postForObject(WXApi.UNIFIED_ORDER, xml, String.class);
		Map<String, String> resultMap = WXApi.convertString2Map(resultXml);
		if (!"SUCCESS".equals(resultMap.get("return_code"))) {
			logger.error("微信统一下单接口调用异常，异常信息：{}", resultMap.get("return_msg"));
			return OutDTO.of(BOOLEAN_STRING_FALSE, resultMap.get("return_msg"));
		}
		if (!"SUCCESS".equals(resultMap.get("result_code"))) {
			logger.error("微信统一下单接口调用异常，错误码：{}，错误信息：{}", resultMap.get("result_code"), resultMap.get("err_code_des"));
			return OutDTO.of(BOOLEAN_STRING_FALSE, resultMap.get("err_code_des"));
		}
		String returnSign = resultMap.get("sign");
		resultMap.remove("sign");
		String sumSign = WXApi.generateSign(resultMap);
		if (!returnSign.equals(sumSign)) {
			logger.error("统一下单返回结果，签名校验不通过。返回的结果：{}，计算的sign：{}", resultXml, sumSign);
			return OutDTO.of(BOOLEAN_STRING_FALSE, "签名校验不通过");
		}
		// 重新签名
		String timestampString = String.valueOf(System.currentTimeMillis() / 1000);
		String nonceString = WXApi.generateNonceStr();
		String packageString = "prepay_id=" + resultMap.get("prepay_id");
		String signType = "MD5";
		signMap.clear();
		signMap.put("appId", appId);
		signMap.put("timeStamp", timestampString);
		signMap.put("nonceStr", nonceString);
		signMap.put("package", packageString);
		signMap.put("signType", signType);
		SubmitOrderOutDTO outData = new SubmitOrderOutDTO();
		outData.setOrderCode(orderCode);
		outData.setAppId(appId);
		outData.setTimestamp(timestampString);
		outData.setPackageString(packageString);
		outData.setNonceString(nonceString);
		outData.setSignType(signType);
		outData.setPaySign(WXApi.generateSign(signMap));
		return OutDTO.of(BOOLEAN_STRING_TRUE, null, null, outData);
	}

}
